Skip to content

feat: route pkg@version from deps, direct nav, and search for any valid specifier#1723

Open
serhalp wants to merge 4 commits intomainfrom
serhalp/fix-version-sorting
Open

feat: route pkg@version from deps, direct nav, and search for any valid specifier#1723
serhalp wants to merge 4 commits intomainfrom
serhalp/fix-version-sorting

Conversation

@serhalp
Copy link
Member

@serhalp serhalp commented Feb 27, 2026

🔗 Linked issue

Closes #1120
Closes #1416

🧭 Context

We have two seemingly separate issues:

  • Dependency links with version ranges (e.g. ^18.0.0 || ^19.0.0, >15 <=16.0.2) currently navigate to a non-existent version page and result in a 404.
  • Searching for nuxt@4.0.1 or nuxt@>=4 or nuxt@>=4<6 (convenient if pasting from somewhere) does not result in a match. There's a proposed temporary solution in feat: strip version info from the search query #1626 to strip the version segment from the query.

📚 Description

  • Keep links to a single package version as is, but for all other version specifiers, link to the package page with the "Filter by semver" input pre-populated! (In the future if/when we have a dedicated versions page we could link to that instead.)
  • Rather than implementing this logic at the "where to link to" layer, I implemented this in the routing layer, i.e. https://npmx.dev/package/nuxt/v/:version will route to the specific versioned package page if version is a specific version string and route to the package page with ?semver=<version> otherwise.
  • ... which means we get a fix to Add possibility to search for specific version of a package #1416 almost for free! This PR makes it so that searching for nuxt@4.0.1 or nuxt@>=4 or nuxt@>=4<6 and pressing Enter now navigates directly to the package version page for a specific version or to the semver-filtered package page for a semver specifier, instead of returning no results. This just works for both exact versions and semver ranges, because of the above routing change.
npmx.smart.pkg@v.routing.mp4

…nstead of 404

Dependency links with version ranges (e.g. "^18.0.0 || ^19.0.0", ">15 <=16.0.2") previously
navigated to a non-existent version page and 404'd.

Now `packageRoute()` distinguishes exact versions from ranges: exact versions link to the version
page, while ranges link to the package page with `?semver=<range>#versions`, pre-populating the
existing "Filter by semver" input.

Closes #1120
Searching for `esbuild@0.25.12` or `@angular/core@^18` and pressing Enter now navigates directly to
the package version page (or semver filter for ranges) instead of returning no results.

Since in the previous commit I added support for linking to the package page with the a pre-filled
semver version specifier in the query string to populate the version filter, this supports both
exact versions and semver ranges in the search input.

Closes #1416
@vercel
Copy link

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 27, 2026 10:23pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Feb 27, 2026 10:23pm
npmx-lunaria Ignored Ignored Feb 27, 2026 10:23pm

Request Review

@serhalp serhalp changed the title feat: intelligently route pkg@version from deps, direct nav, and search for any valid specifier feat: route pkg@version from deps, direct nav, and search for any valid specifier Feb 27, 2026
@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@trueberryless
Copy link
Contributor

Awesome idea, I really like this one. Two birds with one stone 🚀

@serhalp serhalp marked this pull request as ready for review February 28, 2026 17:03
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

This PR adds support for navigating directly to specific package versions from the search interface using "pkg@version" notation. It introduces a new parsePackageSpecifier utility function to parse package specifiers into name and version components, updates the router to differentiate between exact versions and semver ranges, and wires this functionality throughout the application. The changes consolidate package parsing logic previously scattered across multiple files into a single shared utility, with corresponding updates to search navigation, routing logic, and server-side cache handling.

Possibly related PRs

Suggested labels

front

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description directly relates to the changeset, explaining the routing logic changes and search functionality enhancements that address issues #1120 and #1416.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch serhalp/fix-version-sorting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
shared/utils/parse-package-param.ts (1)

54-61: Trailing @ edge case may produce unexpected results.

When the input ends with @ but has no version (e.g., 'esbuild@'), the function returns { name: 'esbuild@' } because the version slice is empty. This retains the trailing @ in the package name, which is not a valid npm package name.

Consider whether this should instead return { name: 'esbuild' } to strip the trailing @:

💡 Suggested fix
 export function parsePackageSpecifier(input: string): { name: string; version?: string } {
   const atIndex = input.startsWith('@') ? input.indexOf('@', 1) : input.indexOf('@')
   if (atIndex > 0) {
     const version = input.slice(atIndex + 1)
     if (version) return { name: input.slice(0, atIndex), version }
+    // Trailing @ with no version - return name without the @
+    return { name: input.slice(0, atIndex) }
   }
   return { name: input }
 }
modules/runtime/server/cache.ts (1)

222-238: Consider using parsePackageSpecifier to reduce code duplication.

This function contains inline parsing logic for extracting the package name and version specifier (lines 226-238) that duplicates the logic in parsePackageSpecifier. The same pattern appears in processSingleVersionsMeta (lines 300-312).

Since parsePackageSpecifier was introduced to centralise this parsing, consider refactoring these functions to use it for consistency and maintainability.

♻️ Suggested refactor
 async function processSingleFastNpmMeta(
   packageQuery: string,
   storage: ReturnType<typeof useStorage>,
   metadata: boolean,
 ): Promise<Record<string, unknown>> {
-  let packageName = packageQuery
-  let specifier = 'latest'
-
-  if (packageName.startsWith('@')) {
-    const atIndex = packageName.indexOf('@', 1)
-    if (atIndex !== -1) {
-      specifier = packageName.slice(atIndex + 1)
-      packageName = packageName.slice(0, atIndex)
-    }
-  } else {
-    const atIndex = packageName.indexOf('@')
-    if (atIndex !== -1) {
-      specifier = packageName.slice(atIndex + 1)
-      packageName = packageName.slice(0, atIndex)
-    }
-  }
+  const { name: packageName, version } = parsePackageSpecifier(packageQuery)
+  const specifier = version ?? 'latest'
test/unit/app/utils/router.spec.ts (1)

100-107: Consider adding a test case for undefined version.

The test covers null version but not undefined. Since the function signature accepts string | null | undefined, it would be good to explicitly test the undefined case for completeness.

💡 Suggested addition
   describe('with null/undefined version', () => {
     it('returns package route for null version', () => {
       expect(packageRoute('react', null)).toEqual({
         name: 'package',
         params: { org: '', name: 'react' },
       })
     })
+
+    it('returns package route for undefined version', () => {
+      expect(packageRoute('react', undefined)).toEqual({
+        name: 'package',
+        params: { org: '', name: 'react' },
+      })
+    })
   })

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85ac3d7 and 2db1c4d.

📒 Files selected for processing (8)
  • app/components/Package/Versions.vue
  • app/pages/search.vue
  • app/utils/router.ts
  • modules/runtime/server/cache.ts
  • shared/utils/parse-package-param.ts
  • test/e2e/search-at-version.spec.ts
  • test/unit/app/utils/router.spec.ts
  • test/unit/shared/utils/parse-package-param.spec.ts

@serhalp serhalp requested a review from danielroe February 28, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add possibility to search for specific version of a package Dependency links with version union ("1.0 || 2.0") resolves to 404 page

2 participants